using System;
using System.IO;
using System.Security;
using System.Text;

namespace NFreeMarker.Cache
{
    /**
     * A {@link TemplateLoader} that uses files in a specified directory as the
     * source of templates. If contains security checks that will prevent it
     * serving templates outside the template directory (like <code>&lt;include /etc/passwd></code>.
     * It compares canonical paths for this, so templates that are symbolically
     * linked into the template directory from outside of it won't work either.
     * @author Attila Szegedi, szegedia at freemail dot hu
     * @version $Id: FileTemplateLoader.java,v 1.26 2004/03/29 08:06:22 szegedia Exp $
     */
    public class FileTemplateLoader : ITemplateLoader
    {
        public readonly DirectoryInfo BaseDir;
        private readonly string _canonicalPath;

        /**
         * Creates a new file template cache that will use the current directory
         * (the value of the system property <code>user.dir</code> as the base
         * directory for loading templates. It will not allow access to template
         * files that are accessible through symlinks that point outside the
         * base directory.
         */
        public FileTemplateLoader()
            : this(new DirectoryInfo(Environment.CurrentDirectory))
        {
        }

        /**
         * Creates a new file template loader that will use the specified directory
         * as the base directory for loading templates. It will not allow access to
         * template files that are accessible through symlinks that point outside 
         * the base directory.
         * @param baseDir the base directory for loading templates
         */
        public FileTemplateLoader(DirectoryInfo baseDir)
            : this(baseDir, false)
        {
        }

        /**
         * Creates a new file template loader that will use the specified directory
         * as the base directory for loading templates.
         * @param baseDir the base directory for loading templates
         * @param allowLinking if true, it will allow 
         */
        public FileTemplateLoader(DirectoryInfo baseDir, bool allowLinking)
        {
            if (!baseDir.Exists) {
                throw new FileNotFoundException(baseDir + " does not exist.");
            }

            if (allowLinking)
            {
                BaseDir = baseDir;
                _canonicalPath = null;
            }
            else
            {
                BaseDir = baseDir;
                _canonicalPath = BaseDir.FullName;
            }
        }
        
        public object FindTemplateSource(string name)
        {
            var source = new FileInfo(Path.Combine(BaseDir.FullName, name));
            if (!source.Exists)
            { 
                return null;
            }

            // Security check for inadvertently returning something 
            // outside the template directory when linking is not 
            // allowed.
            if(_canonicalPath != null)
            {
                string normalized = source.FullName;
                if (!normalized.StartsWith(_canonicalPath))
                {
                    throw new SecurityException(source.FullName 
                            + " resolves to " + normalized + " which " + 
                            " doesn't start with " + _canonicalPath);
                }
            }
            return source;
        }
        
        public long GetLastModified(object templateSource)
        {
            return ((FileInfo) templateSource).LastWriteTime.ToFileTime();
        }
        
        public TextReader GetReader(object templateSource, Encoding encoding)
        {
            if (!(templateSource is FileInfo))
            {
                throw new ArgumentException(
                        "templateSource is a: " + 
                        templateSource.GetType().FullName);
            }
            return new StreamReader(((FileInfo) templateSource).FullName, encoding);
        }
        
        public void CloseTemplateSource(object templateSource)
        {
            // Do nothing.
        }
    }
}